<script setup lang="ts">
import type { Ref } from 'vue';
import dayjs from 'dayjs';
import _ from 'lodash';
import { Search, Refresh } from '@element-plus/icons-vue';

import SubViewContainer from '@core/components/subLayout/SubViewContainer.vue';
import TableWithPagination from '@core/components/TableWithPagination.vue';
import * as Enums from '@firewall/engine/Enums';
import LogService, { FetchRawLogParams, RawLog, LogSetting } from '@firewall/engine/LogService';
import { FirewallSummary } from '@firewall/engine/Firewall';
import Application from '@firewall/engine/Application';
import Ips from '@firewall/engine/Ips';

const { t } = useI18n({ inheritLocale: true, useScope: 'local' });
const SearchType = {
  SourceIp: 'sourceIp',
  DestinationIp: 'destinationIp',
};

const isTableLoading = ref(false);

const logs: Ref<Array<RawLog>> = ref([]);
const firewallInstance = reactive({} as Partial<FirewallSummary>);
const logSetting = reactive({} as Partial<LogSetting>);
const refreshLogSetting = async () => {
  if (!firewallInstance.firewallId) {
    return;
  }
  isTableLoading.value = true;
  try {
    const remoteSetting = await LogService.fetchSetting({ firewallId: firewallInstance.firewallId });
    Object.assign(logSetting, remoteSetting);
  } finally {
    isTableLoading.value = false;
  }
};
const filters = reactive({
  ipVersion: Enums.IpVersion.V4,
  timeRange: [
    dayjs().add(-1, 'days').format(Enums.REMOTE_DATE_FORMATTER),
    dayjs().format(Enums.REMOTE_DATE_FORMATTER),
  ],
  app: Enums.FILTER_ALL,
  ipsRule: Enums.FILTER_EMPTY,
  level: Enums.ActionLevel.All,
  origin: Enums.LogOrigin.All,
  direction: Enums.NetworkDirection.All,
  searchType: SearchType.SourceIp,
  searchKeyword: '',
});
const pagination = reactive({
  pageSize: 10,
  currentPage: 1,
  total: 0,
});
const refreshTable = async () => {
  const fetchParams = getFetchParams();
  if (!fetchParams) {
    return;
  }
  isTableLoading.value = true;
  try {
    const response = await LogService.fetchRawLogs(fetchParams);
    logs.value = response.raw_logs || [];
    pagination.total = response.total || 0;
  } finally {
    isTableLoading.value = false;
  }
  LogService.fetchRuleNameOfLogs(logs.value, firewallInstance.firewallId);
};
const getFetchParams = (): FetchRawLogParams | null => {
  if (!logSetting.logUserId || !logSetting.auditGroupId || !logSetting.auditAttackId) {
    return null;
  }
  return {
    userid: logSetting.logUserId,
    loggroup: logSetting.auditGroupId,
    logunit: logSetting.auditAttackId,
    page: pagination.currentPage,
    size: pagination.pageSize,
    start_time: dayjs(filters.timeRange[0]).format(Enums.REMOTE_DATE_FORMATTER),
    end_time: dayjs(filters.timeRange[1]).format(Enums.REMOTE_DATE_FORMATTER),
    is_full_index: Enums.RequestBoolean.True,
    judge_mode: Enums.LogJudgeMode.Equal,
    keys: [
      { key: 'ip_proto', value: filters.ipVersion, judge_mode: Enums.LogJudgeMode.Equal },
      { key: 'app_id', value: filters.app, judge_mode: Enums.LogJudgeMode.Equal },
      { key: 'class', value: filters.ipsRule, judge_mode: Enums.LogJudgeMode.FuzzyMatching },
      { key: 'level', value: filters.level, judge_mode: Enums.LogJudgeMode.Equal },
      { key: 'origin', value: filters.origin, judge_mode: Enums.LogJudgeMode.Equal },
      { key: 'dir', value: filters.direction, judge_mode: Enums.LogJudgeMode.Equal },
      {
        key: 's_ip',
        value: filters.searchType === SearchType.SourceIp ? filters.searchKeyword : '',
        judge_mode: Enums.LogJudgeMode.FuzzyMatching,
      },
      {
        key: 'd_ip',
        value: filters.searchType === SearchType.DestinationIp ? filters.searchKeyword : '',
        judge_mode: Enums.LogJudgeMode.FuzzyMatching,
      },
    ],
  };
};

const onFirewallChange = async (newInstance: FirewallSummary) => {
  Object.assign(firewallInstance, newInstance);
  await Application.getSummaryList();
  await Ips.getMethodSummaryList();
  await refreshLogSetting();
  await refreshTable();
};
const onFiltersChange = async () => {
  await refreshTable();
};
const formatIpsRuleName = (namePair: string) => {
  const [parentName, childName] = namePair.split('-');
  const { parent, child } = Ips.getMethodByName(parentName, childName);
  return _.compact([parent?.nameCn, child?.nameCn]).join('-');
};
const DefenseActionClassMap: Record<number, string> = {
  1: 'pass',
  2: 'block',
  3: 'warn',
};
</script>

<template>
  <SubViewContainer>
    <ElCard v-loading="isTableLoading">
      <div class="table-controller">
        <FirewallInstanceSelector
          :firewall="firewallInstance"
          class="firewall-instance-selector"
          @update:firewall="onFirewallChange"
        />
        <div class="filters">
          <!-- TODO: 等防火墙支持 ipv6 后，重新开启 ip version 选择器 -->
          <!-- <IpVersionSelector v-model:version="filters.ipVersion" /> -->
          <ElDatePicker
            v-model="filters.timeRange"
            type="datetimerange"
            :format="t('datetimeFormatter')"
            :value-format="Enums.REMOTE_DATE_FORMATTER"
          />
          <ApplicationSelector v-model:application="filters.app" />
          <IpsRuleSelector v-model:rule="filters.ipsRule"  />
          <ActionLevelSelector v-model:level="filters.level" />
          <LogOriginSelector v-model:origin="filters.origin" />
          <NetworkDirectionSelector v-model:direction="filters.direction" />
          <ElInput
            v-model="filters.searchKeyword"
            class="search-input"
            :placeholder="t('inputToSearchPlease')"
            @keyup.enter="onFiltersChange"
          >
            <template #prepend>
              <ElSelect v-model="filters.searchType">
                <ElOption
                  v-for="item in SearchType"
                  :key="item"
                  :label="t(`searchType.${item}`)"
                  :value="item"
                />
              </ElSelect>
            </template>
            <template #append>
              <ElButton :icon="Search" @click="onFiltersChange" />
            </template>
          </ElInput>
          <ElButton :icon="Refresh" @click="onFiltersChange" />
        </div>
      </div>
      <TableWithPagination
        v-model:paginationPageSize="pagination.pageSize"
        v-model:paginationCurrentPage="pagination.currentPage"
        size="small"
        :data="logs"
        :empty-text="t(firewallInstance.firewallId ? 'listIsEmpty' : 'noSelectFirewallYet')"
        :paginationTotal="pagination.total"
        :onUpdate:paginationPageSize="onFiltersChange"
        :onUpdate:paginationCurrentPage="onFiltersChange"
        paginationLayout="sizes, total, prev, pager, next"
      >
        <ElTableColumn :label="t('recordedAt')" min-width="120">
          <template #default="{ row }">
            {{dayjs.unix(row.time).format(t('dateMinuteFormatter'))}}
          </template>
        </ElTableColumn>
        <ElTableColumn :label="t('sourceIp')" prop="s_ip" />
        <ElTableColumn :label="t('destinationIp')" prop="d_ip" />
        <ElTableColumn :label="t('destinationPort')" prop="d_port" width="70" />
        <ElTableColumn :label="t('application')" >
          <template #default="{ row }">
            {{Application.getOneById(row.app_id)?.name || '----'}}
          </template>
        </ElTableColumn>
        <ElTableColumn :label="t('intrusionType')" min-width="140">
          <template #default="{ row }">
            {{formatIpsRuleName(row.class)}}
          </template>
        </ElTableColumn>
        <ElTableColumn :label="t('intrusionLevel')" width="60">
          <template #default="{ row }">
            <ElTag :class="_.lowerCase(row.level)">{{t(`intrusionLevels.${row.level}`)}}</ElTag>
          </template>
        </ElTableColumn>
        <ElTableColumn :label="t('ruleId')" prop="rule_id" width="90">
          <template #default="{ row }">{{row.rule_id || '----'}}</template>
        </ElTableColumn>
        <ElTableColumn :label="t('ruleName')" min-width="160" show-overflow-tooltip>
          <template #default="{ row }">{{row.rule_name || '----'}}</template>
        </ElTableColumn>
        <ElTableColumn :label="t('origin')" min-width="100">
          <template #default="{ row }">{{t(`origins.${row.origin}`)}}</template>
        </ElTableColumn>
        <ElTableColumn :label="t('direction')" min-width="80">
          <template #default="{ row }">{{t(`directions.${row.dir}`)}}</template>
        </ElTableColumn>
        <ElTableColumn :label="t('action')" min-width="80">
          <template #default="{ row }">
            <span class="defense-action" :class="DefenseActionClassMap[row.action]">
              {{t(`actions.${row.action}`)}}
            </span>
          </template>
        </ElTableColumn>
      </TableWithPagination>
    </ElCard>
  </SubViewContainer>
</template>

<i18n lang="yaml" locale="zh-CN">
  datetimeFormatter: 'YYYY-MM-DD HH:mm:ss'
  dateMinuteFormatter: 'YYYY-MM-DD HH:mm'
  searchType:
    sourceIp: '来源 IP'
    destinationIp: '目的 IP'
  inputToSearchPlease: '输入搜索'
  listIsEmpty: '暂无数据'
  noSelectFirewallYet: '请先选择配置的防火墙实例'
  recordedAt: '发生时间'
  sourceIp: '源IP地址'
  destinationIp: '目的IP地址'
  destinationPort: '目的端口'
  application: '应用'
  intrusionType: '攻击类型'
  intrusionLevel: '等级'
  intrusionLevels:
    High: '高危'
    Middle: '中危'
    Low: '低危'
  ruleId: '命中规则 ID'
  ruleName: '命中规则名称'
  origin: '判断来源'
  origins:
    1: '基础防御'
    2: '虚拟补丁'
  direction: '方向'
  directions:
    1: '内对外'
    2: '外对内'
  action: '防御状态'
  actions:
    1: '放行'
    2: '拦截'
    3: '警告'
</i18n>

<style lang="scss" scoped>
.table-controller {
  display: flex;
  flex-direction: column;

  .firewall-instance-selector {
    margin-bottom: 10px;
  }

  .filters {
    display: flex;
    flex-flow: wrap;
    align-self: flex-end;
    justify-content: flex-end;
    max-width: 1200px;

    & > :deep(*) {
      flex-grow: 0;
      margin: 0 0 10px 10px;
    }

    .filter-placeholder {
      width: 138px;
      height: 32px;
      border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
      box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
    }
  }

  .search-input {
    width: 440px;

    :deep(.el-input__inner) {
      line-height: var(--el-input-height);
    }

    .el-select {
      width: 100px;
    }
  }
}
</style>
